package com.joeshing.util.bean.compare;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

/**
 * 类对比方法
 * <p>
 * 根据Jacky早前的方法进行修改
 * 
 * @author joeshing
 * @date 2012-11-13
 * 
 */
public class ModelCompareUtil {

	public static final String VALUE_PREFIX = "VALUE_";
	public static final String DATA_FORMAT_KEY = "MODE_DATA_FORMAT";
	public static final String DATA_DECIMAL_FORMAT_KEY = "MODE_DATA_DECIMAL_FORMAT";

	private static final String GET_METHOD_PREFIX = "get";
	private static final String DATA_FORMAT_STRING = "yyyy-MM-dd HH:mm";
	private static final String DECIMAL_FORMAT_STR = "0.00";


	public static List<CompareResult> compare(Object before, Object after) {
		return compare(before, after, new HashMap<String, String>(0), true, "");
	}

	public static List<CompareResult> compare(Object before, Object after, Map<String, String> changeAdapter) {
		return compare(before, after, changeAdapter, true, "");
	}

	public static List<CompareResult> compare(Object before, Object after, Map<String, String> changeAdapter, String... escaptField) {
		return compare(before, after, changeAdapter, true, escaptField);
	}

	/**
	 * 默认对比的是返回类型是：String, int, double, date, char, long, boolean,
	 * float的参数为0的“get”方法，要求两个实体都有相同的方法(返回类型可不同)才会进行对比。若为date类型，请在map中写入
	 * {@link #DATA_FORMAT_KEY}
	 * (默认格式为："yyyy-MM-dd HH:mm");若为float或double类型，转字符串时默认精确2位后比较，
	 * 如要自定义精确转换位数请个map中写入{@link #DATA_DECIMAL_FORMAT_KEY}
	 * 
	 * @param before
	 *            实体，必须初始化
	 * @param after
	 *            提交表单form,必须初始化
	 * @param changeAdapter
	 *            ：key
	 *            为field名，value为将field替换成的名，若为空，则取field名。或把状态数字转换成有意义的值，其样式为
	 *            ：field +"_"+ field值
	 * @param isEscapt
	 *            ：是否是要比较的field
	 * @param field
	 *            ：比较或不比较的field,字符或字符数组，取决于isEscapt
	 * @return 比较结果
	 */
	/**
	 * @param before
	 * @param after
	 * @param changeAdapter
	 * @param isEscapt
	 * @param field
	 * @return
	 */
	public static List<CompareResult> compare(Object before, Object after, Map<String, String> changeAdapter, boolean isEscapt,
			String... field) {
		if (before == null || after == null) {
			return new ArrayList<CompareResult>(0);
		}
		Method[] beforeMethods = before.getClass().getMethods();
		Map<String, String> beforeMethodsMap = toMethodsNameMap(after);
		Map<String, String> escaptFieldMap = toEscaptMap(field);

		// 返回结果集
		List<CompareResult> compareResults = new ArrayList<CompareResult>(beforeMethods.length);

		String methodName;
		String fieldName;
		String beforeValue;
		String afterValue;
		String fieldReplaceName;
		for (Method method : beforeMethods) {
			methodName = method.getName();
			fieldName = getField(methodName);
			if (isEscapt ? escaptFieldMap.containsKey(fieldName) : !escaptFieldMap.containsKey(fieldName)) {
				continue;
			}
			if (isGetMethod(method) && beforeMethodsMap.containsKey(methodName)) {
				try {
					beforeValue = trim(method.invoke(before, null), method, changeAdapter);
					afterValue = trim(after.getClass().getMethod(methodName, null).invoke(after, null), method,
							changeAdapter);
					if (afterValue != null) {
						if (!afterValue.equals(beforeValue)) {
							fieldReplaceName = changeAdapter.containsKey(fieldName) ? changeAdapter.get(fieldName)
									: camelCaseToWord(fieldName);

							CompareResult result = new CompareResult();
							result.setField(fieldName);
							result.setTitle(fieldReplaceName == null ? fieldName : fieldReplaceName);
							result.setAfter(getAdapterValue(fieldName, afterValue, changeAdapter));
							result.setBefore(getAdapterValue(fieldName, beforeValue, changeAdapter));
							compareResults.add(result);
						}
					}
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				}
			}
		}
		return compareResults;

	}
	
	private static String getAdapterValue(String field, String value, Map<String, String> changeAdapter) {
		if (!StringUtils.isEmpty(value)) {
			if(changeAdapter.containsKey(field+"_"+value)){
				return changeAdapter.get(field+"_"+value);
			}
			if (changeAdapter.containsKey(VALUE_PREFIX + value)) {
				return changeAdapter.get(VALUE_PREFIX + value);
			}
		}
		return value;
	}

	@SuppressWarnings("unchecked")
	private static String trim(Object input, Method method, Map<String, String> changeAdapter) {
		if (input == null) {
			return "";
		}
		Class returnClazz = method.getReturnType();
		String type = returnClazz.getName();
		if (type.equals("java.lang.String")) {
			String toString = String.valueOf(input);
			if (!StringUtils.isEmpty(toString)) {
				return "";
			} else {
				return toString;
			}
		} else if (type.equals("double") || type.equals("java.lang.Double") || type.equals("float")
				|| type.equals("java.lang.Float")) {
			DecimalFormat decimalFormat = new DecimalFormat(
					changeAdapter.containsKey(DATA_DECIMAL_FORMAT_KEY) ? changeAdapter.get(DATA_DECIMAL_FORMAT_KEY)
							: DECIMAL_FORMAT_STR);
			return decimalFormat.format(input);
		} else if (type.equals("java.util.Date")) {
			SimpleDateFormat dateFormat = new SimpleDateFormat(
					changeAdapter.containsKey(DATA_FORMAT_KEY) ? changeAdapter.get(DATA_FORMAT_KEY)
							: DATA_FORMAT_STRING);
			return dateFormat.format((Date) input);
		} else {
			return String.valueOf(input);
		}
	}

	private static String getField(String input) {
		return String.valueOf(input.charAt(3)).toLowerCase() + input.substring(4);
	}

	private static Map<String, String> toEscaptMap(String... escaptField) {
		if (escaptField != null) {
			List<String> fieldList = Arrays.asList(escaptField);
			Map<String, String> escaptFieldMap = new HashMap<String, String>(fieldList.size() * 2);
			for (String string : fieldList) {
				escaptFieldMap.put(string, string);
			}
			return escaptFieldMap;
		}
		return new HashMap<String, String>(0);
	}

	private static Map<String, String> toMethodsNameMap(Object form) {
		Method[] formMethods = form.getClass().getMethods();
		Map<String, String> methodsMap = new HashMap<String, String>(formMethods.length * 2);
		String methodName;
		for (Method method : formMethods) {
			methodName = method.getName();
			methodsMap.put(methodName, methodName);
		}
		return methodsMap;
	}

	@SuppressWarnings("unchecked")
	private static boolean isGetMethod(Method method) {
		if (method.getName().startsWith(GET_METHOD_PREFIX)) {
			if (method.getParameterTypes().length == 0) {
				Class returnClazz = method.getReturnType();
				String type = returnClazz.getName();
				if (type.equals("java.lang.String")) {
					return true;
				} else if (type.equals("int") || type.equals("java.lang.Integer")) {
					return true;
				} else if (type.equals("double") || type.equals("java.lang.Double")) {
					return true;
				} else if (type.equals("java.util.Date")) {
					return true;
				} else if (type.equals("char") || type.equals("java.lang.Character")) {
					return true;
				} else if (type.equals("long") || type.equals("java.lang.Long")) {
					return true;
				} else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
					return true;
				} else if (type.equals("float") || type.equals("java.lang.Float")) {
					return true;
				}
			}
		}
		return false;
	}

	private static String camelCaseToWord(String camelCaseText) {

		if (camelCaseText.equals(camelCaseText.toUpperCase())) {
			return camelCaseText;
		}

		char[] ch = camelCaseText.toCharArray();
		String first = "" + ch[0];
		StringBuffer build = new StringBuffer(first.toUpperCase());

		for (int i = 1; i < ch.length; i++) {
			String test = "" + ch[i];

			if (test.equals(test.toUpperCase())) {
				build.append(" ");
			}

			build.append(test);
		}

		return build.toString();
	}

}
